package com.xyzboom.compiler.compiler.lexer

import com.xyzboom.compiler.excepitons.compiler.lexer.IllegalLexerException
import com.xyzboom.compiler.fa.DFA
import mu.KotlinLogging

private val logger = KotlinLogging.logger { }

/**
 * Parse lexer
 *
 * @param lexerFileStr
 * @return list of lexer rule
 */
fun parseLexer(lexerFileStr: String): MutableList<LexerRule> {
    val lexerLines = lexerFileStr.lines()
    var line = 0
    HashMap<String, DFA<Int, String>>()
    val lexerRuleMap = HashMap<String, LexerRule>()
    val lexerRegexMap = HashMap<String, String>()
    val lexerRules = ArrayList<LexerRule>()
    while (true) {
        if (line >= lexerLines.size) break
        val nowLineStr = lexerLines[line]
        val splitNow = nowLineStr.split(Regex("\\s+"), 4)
        if (splitNow.size != 3 && splitNow.size != 4)
            throw IllegalLexerException.expectRuleNameAndType(line)
        val ruleName = splitNow[0]
        val typeName = splitNow[1]
        val ruleRegex = splitNow[2]
        var i = 0
        val trueRegexSb = StringBuilder()
        while (true) {
            if (i >= ruleRegex.length) break
            when {
                ruleRegex[i] == '\\' -> {
                    trueRegexSb.append(ruleRegex[i])
                    if (i < ruleRegex.length) {
                        trueRegexSb.append(ruleRegex[i + 1])
                    }
                    i += 2
                    continue
                }

                ruleRegex[i] == '{' -> {
                    var ruleI = i + 1
                    val ruleToReplaceSB = StringBuilder()
                    while (ruleRegex[ruleI] != '}') {
                        ruleToReplaceSB.append(ruleRegex[ruleI])
                        ruleI++
                        if (ruleI >= ruleRegex.length) throw IllegalLexerException.lackOfChar('}', line)
                    }
                    val regexToReplace = lexerRegexMap[ruleToReplaceSB.toString()]
                        ?: throw IllegalLexerException.noSuchRuleException(
                            line, nowLineStr.indexOf(ruleRegex) + i + 1, ruleToReplaceSB.toString(), nowLineStr
                        )
                    trueRegexSb.append('(')
                    trueRegexSb.append(regexToReplace)
                    trueRegexSb.append(')')
                    i = ruleI + 1
                    continue
                }

                else -> {
                    trueRegexSb.append(ruleRegex[i])
                }
            }
            i++
        }
        val trueRegex = trueRegexSb.toString()
        lexerRegexMap[ruleName] = trueRegex
        val lexerRule = LexerRule(ruleName, typeName, trueRegex)
        lexerRuleMap[ruleName] = lexerRule
        if (splitNow.size == 4) lexerRule.greedy = false
        logger.trace { lexerRule }
        lexerRules.add(lexerRule)
        line++
    }
    return lexerRules
}